/*
//      k2rtdev.c
//      
// 		Copyright (C) 2010- Valentina Vuksic <v@valvuk.ch>
//      Copyright (C) 2010- Beat Michel Liechti <bml@maduraheights.ch>
//      
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//      
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//      
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.
*/

#include <linux/module.h>
#include <linux/ioport.h>
#include <rtdm/rtdm_driver.h>
#include <linux/time.h>

/* -- required for architecture specific low-level gpio */
#include <mach/pxa-regs.h>
#include <mach/pxa2xx-regs.h>
#include <mach/pxa2xx-gpio.h>
#include <mach/pxa2xx_spi.h>
#include <mach/ssp.h>
#include <mach/regs-ssp.h>
#include <mach/gpio.h>

/* -- module metadata --------------------------------------------------------*/

MODULE_LICENSE("GPL");
MODULE_AUTHOR("v@valvuk.ch,bml@maduraheights.ch");
MODULE_DESCRIPTION("coffee2dev");

/* -- defines ----------------------------------------------------------------*/

#define SIZE_MAX			1024
#define DEVICE_NAME			"k2rtdev"
#define SOME_SUB_CLASS		27442		// "k2" = 0x6B32 = 27442

/* -- leds, switches and other resources are memory mapped */

#define MAP_SIZE		(6*4096)		/*! Six pages */
#define IO_BASE			(0xc000000 )	/*! IO base in physical memory */
#define LED_OFFSET		(0x3000)		/*! Offset of led register in IO space */
#define SWITCH_OFFSET	(0x3200)		/*! Offset of switch register in IO space */

/* -- GPIO and labels for buttons */

#define GPIO_T0 99
#define GPIO_T1 101
#define GPIO_T2 102
#define GPIO_T3 103
#define LABL_T0 "T0"
#define LABL_T1 "T1"
#define LABL_T2 "T2"
#define LABL_T3 "T3"

/* -- defines for PWM */

#define GPIO_PWM3_SPEED		12
#define GPIO_PWM3_DIRECTION	21
#define LABL_PWM3_SPEED		"PWM3_SPEED"
#define LABL_PWM3_DIRECTION	"PWM3_DIRECTION"

#define PWMCR3  __REG(0x40C00010) /* PWM Control Register 3 */
#define PWMDCR3 __REG(0x40C00014) /* PWM Duty Cycle Register 3 */
#define PWMPCR3 __REG(0x40C00018) /* PWM Period Control Register 3 */

/* -- defines for strobo */

#define GPIO_SSPSCLK	23
#define GPIO_SSPSFRM	24
#define GPIO_SSPTxD		25
#define	GPIO_SSPRxD		26
#define GPIO_SPI_CS		22
#define GPIO_INDEX		35
#define GPIO_ROTARY		36
#define GPIO_TRIGGER	16

#define LABL_SSPSCLK	"SSPSCLK"
#define LABL_SSPSFRM	"SPSFRM"
#define LABL_SSPTxD		"SSPTxD"
#define LABL_SSPRxD		"SSPRxD"
#define LABL_SPI_CS		"SPI_CS"
#define LABL_INDEX		"INDEX"
#define LABL_ROTARY		"ROTARY"
#define LABL_TRIGGER	"TRIGGER"

#define SSCR0_P1  __REG(0x41000000)  /* SSP Port 1 Control Register 0 */
#define SSCR1_P1  __REG(0x41000004)  /* SSP Port 1 Control Register 1 */
#define SSSR_P1   __REG(0x41000008)  /* SSP Port 1 Status Register */
#define SSITR_P1  __REG(0x4100000C)  /* SSP Port 1 Interrupt Test Register */
#define SSDR_P1   __REG(0x41000010)  /* (Write / Read) SSP Port 1 Data Write Register/SSP Data Read Register */

/* -- function declarations --------------------------------------------------*/

static int k2rtdev_open_nrt(
					struct rtdm_dev_context *context,
					rtdm_user_info_t * user_info, 
					int oflags);

static int k2rtdev_close_nrt(
					struct rtdm_dev_context *context,
					rtdm_user_info_t * user_info);
					
static int k2rtdev_ioctl_nrt(
					struct rtdm_dev_context *context, 
					rtdm_user_info_t *user_info, 
					unsigned int request, 
					void __user *arg);

static ssize_t k2rtdev_write_rt(
					struct rtdm_dev_context *context,
				    rtdm_user_info_t * user_info,
				    const void *buf, 
				    size_t nbyte);

static int button_irq_handler_rt(
					rtdm_irq_t *irq_handle);
					
static int index_irq_handler_rt(
					rtdm_irq_t *irq_handle);
					
static int rotary_irq_handler_rt(
					rtdm_irq_t *irq_handle);
					
static int map_byte_to_wheelchar(unsigned char val);

static int init_gpio(
					int gpio, 
					char *label, 
					int flags);

int __init k2rtdev_init(void);
void __exit k2rtdev_exit(void);

/* -- structs ----------------------------------------------------------------*/

/* -- data structure type for this device */  
typedef struct k2rtdev_context_s {
	/* -- rotary counter */
	atomic_t rotary_val;
	/* -- character to show */
	atomic_t character_val;
	/* -- counter for show */
	atomic_t showcount_val;
	/* -- wheelspeed shadow register */
	atomic_t wheelspeed_val;
	/* -- event to indicate a character has been written */
	rtdm_event_t character_written;
	/* -- mutex to ensure exclusive access to wheel when writing */ 
	rtdm_mutex_t write_mutex;
	/* -- mutex to protect wheel speed register (PWMDCR3) */ 
	rtdm_mutex_t wheelspeed_mutex;
	/* -- memory mapping */
	struct resource *mem_res;
	void *io_base;
	/* t0 irq handle */
	rtdm_irq_t irq_t0_h;
	/* t1 irq handle */
	rtdm_irq_t irq_t1_h;
	/* t2 irq handle */
	rtdm_irq_t irq_t2_h;
	/* t3 irq handle */
	rtdm_irq_t irq_t3_h;
	/* index irq handle */
	rtdm_irq_t irq_index_h;
	/* rotary irq handle */
	rtdm_irq_t irq_rotary_h;
} k2rtdev_context_t;

/* -- static data ------------------------------------------------------------*/

/* -- data structure for this device */ 
static k2rtdev_context_t this;

/* -- RTDM structure describing the device */
static struct rtdm_device device = {
	.struct_version = RTDM_DEVICE_STRUCT_VER,

	.device_flags = RTDM_NAMED_DEVICE,
	.context_size = sizeof(k2rtdev_context_t),
	.device_name = DEVICE_NAME,

	.open_rt = NULL,
	.open_nrt = k2rtdev_open_nrt,

	.ops = {
		.close_rt = NULL,
		.close_nrt = k2rtdev_close_nrt,
		.ioctl_rt = NULL,
		.ioctl_nrt = k2rtdev_ioctl_nrt,
		.read_rt = NULL,
		.read_nrt = NULL,
		.write_rt = k2rtdev_write_rt,
		.write_nrt = NULL,
	},

	.device_class = RTDM_CLASS_EXPERIMENTAL,
	.device_sub_class = RTDM_SUBCLASS_GENERIC,
	.profile_version = 1,
	.driver_name = "k2rtdev",
	.driver_version = RTDM_DRIVER_VER(0, 1, 0),
	.peripheral_name = "k2kaffee RT device",
	.provider_name = "valvuk/bml",
	.proc_name = device.device_name,
};

/* -- open the device */
static int k2rtdev_open_nrt(
					struct rtdm_dev_context *context,
					rtdm_user_info_t * user_info, 
					int oflags)
{
	return 0;
}

/* -- close the device */
static int k2rtdev_close_nrt(
					struct rtdm_dev_context *context,
					rtdm_user_info_t * user_info)
{
	return 0;
}

/* -- ioctl for the device ==> set speed of the wheel */
static int k2rtdev_ioctl_nrt(
					struct rtdm_dev_context *context, 
					rtdm_user_info_t *user_info, 
					unsigned int request, 
					void __user *arg)
{
	/* -- acquire mutex to ensure exclusive access to speed register */
	ssize_t retval = rtdm_mutex_lock(&this.wheelspeed_mutex);
	if (retval == 0)
	{
		/* -- make sure the requested speed is within limits */
		if (request < 0) {
			request = 0;
		}
		if (request > 100) {
			request = 100;
		}
		/* -- write speed register and shadow register */
		PWMDCR3 = request;
		atomic_set(&this.wheelspeed_val, request);
		/* -- set flash duration */
		SSDR_P1 = 200 - request;
		/* -- release mutex */
		rtdm_mutex_unlock(&this.wheelspeed_mutex);
	}
	return retval;
}

/* -- write a string to the device ==> text is shown on the wheel */
static ssize_t k2rtdev_write_rt(
					struct rtdm_dev_context *context,
				    rtdm_user_info_t * user_info,
				    const void *buf, size_t nbyte)
{	
	/* -- acquire mutex to ensure exclusive access to wheel */
	ssize_t retval = rtdm_mutex_lock(&this.write_mutex);
	if (retval == 0) 
	{
		/* -- read wheel speed from shadow register */
		int wheelspeed = atomic_read(&this.wheelspeed_val);
		/* -- check that the wheel is rotating */
		char val;
		int i, wheelch;
		/* -- go through bytes, "write" to wheel */
		for (i = 0; i < nbyte; i++)	{
			/* -- copy byte from user space */
			if (rtdm_copy_from_user(user_info, &val, buf+i, sizeof(val)) == 0) {
				/* -- map byte to wheel character (position), check if valid mapping */
				wheelch = map_byte_to_wheelchar(val);
				if (wheelch >= 0)	{
					/* -- log character to be displayed on wheel */
					rtdm_printk(KERN_DEBUG "k2rtdev_write_rt setting character_val to %d\n", wheelch);
					/* -- set character to be "written" by rotory irq handler */ 
					atomic_set(&this.character_val, wheelch);
					/* -- set showcount which is directly proportional to wheel speed 
					 * -- because a character is shown for one second and wheel speed
					 * -- is equivalent to rotations per second */ 
					atomic_set(&this.showcount_val, wheelspeed);
					if (wheelspeed > 0) {
						/* -- wait until character has been "written" */
						rtdm_event_wait(&this.character_written);
					} else {
						/* -- Superfeature / Hack */
						atomic_set(&this.wheelspeed_val, 0);
						wheelch--;
						if (wheelch < 0) {
							wheelch = 34;
						}
						atomic_set(&this.character_val, wheelch);
						atomic_set(&this.showcount_val, 1);
						/* -- move wheel with short impulses until the desired position has been reached */
						PWMDCR3 = 3;
						while (rtdm_event_timedwait(&this.character_written, 1000, NULL) == -ETIMEDOUT)	{
							PWMDCR3 = 0;
							if (rtdm_event_timedwait(&this.character_written, 1000, NULL) != -ETIMEDOUT) {
								break;
							}
							PWMDCR3 = 3;
						}
					}
				}
			}
		}
		/* -- release mutex */
		rtdm_mutex_unlock(&this.write_mutex);
		/* -- set return value */
		retval = nbyte;
	}
	return retval;
}

/* -- RT IRQ handler for buttons --*/
static int button_irq_handler_rt(
					rtdm_irq_t *irq_handle)
{	
	/* -- same handler for all IRQs ==> use handle to identify source */
	if (irq_handle == &this.irq_t0_h) {
		rtdm_printk(KERN_DEBUG "button_irq_handler called for GPIO_T0\n");
	}	 
	else if (irq_handle == &this.irq_t1_h) {
		rtdm_printk(KERN_DEBUG "button_irq_handler called for GPIO_T1\n");
	}
	else if (irq_handle == &this.irq_t2_h) {
		rtdm_printk(KERN_DEBUG "button_irq_handler called for GPIO_T2\n");
	}
	else if (irq_handle == &this.irq_t3_h) {
		rtdm_printk(KERN_DEBUG "button_irq_handler called for GPIO_T3\n");
	}
	return RTDM_IRQ_HANDLED;
}
	
/* -- RT IRQ handler for index --*/
static int index_irq_handler_rt(
					rtdm_irq_t *irq_handle)
{
	/* -- trigger the strobo */
	//GPSR(GPIO_TRIGGER) = GPIO_bit(GPIO_TRIGGER);
	//GPCR(GPIO_TRIGGER) = GPIO_bit(GPIO_TRIGGER);
	/* -- reset rotary value to zero */
	atomic_set(&this.rotary_val, 0);
	return RTDM_IRQ_HANDLED;
}

/* -- RT IRQ handler for rotary --*/
static int rotary_irq_handler_rt(
					rtdm_irq_t *irq_handle)
{
	/* -- update rotary value (current postion) */
	int rotary = atomic_inc_return(&this.rotary_val) - 1;
	/* -- read character and check if the current position is matching */
	int character = atomic_read(&this.character_val);
	if (character >= 0 && character == rotary)
	{
		/* -- read wheel speed from shadow register */
		int wheelspeed = atomic_read(&this.wheelspeed_val);
		/* -- read show counter and check if it's above zero */
		int showcount = atomic_read(&this.showcount_val);
		if (showcount > 0) {
			if (wheelspeed > 0)	{
				/* -- trigger the strobo */
				GPSR(GPIO_TRIGGER) = GPIO_bit(GPIO_TRIGGER);
				GPCR(GPIO_TRIGGER) = GPIO_bit(GPIO_TRIGGER);
			}
			/* -- decrease the show counter */
			showcount = atomic_dec_return(&this.showcount_val);	
			if (showcount == 0)
			{
				/* -- raise "character written" event */
				rtdm_event_signal(&this.character_written);
			}
		}
	}
	return RTDM_IRQ_HANDLED;
}

/* -- mapping function for wheel characters */
static int map_byte_to_wheelchar(unsigned char val)
{
	int retval = -1;
	/* -- map byte to rotary value (use lookup table?) */
	switch (val) {
		case 'A': retval = 0; break;
		case 'B': retval = 1; break;
		case 'C': retval = 2; break;
		case 'D': retval = 3; break;
		case 'E': retval = 4; break;
		case 'F': retval = 5; break;
		case 'G': retval = 6; break;
		case 'H': retval = 7; break;
		case 'I': retval = 8; break;
		case 'J': retval = 9; break;
		case 'K': retval = 10; break;
		case 'L': retval = 11; break;
		case 'M': retval = 12; break;
		case 'N': retval = 13; break;
		case 'O': retval = 14; break;
		case 'P': retval = 15; break;
		case 'Q': retval = 16; break;
		case 'R': retval = 17; break;
		case 'S': retval = 18; break;
		case 'T': retval = 19; break;
		case 'U': retval = 20; break;
		case 'V': retval = 21; break;
		case 'W': retval = 22; break;
		case 'X': retval = 23; break;
		case 'Y': retval = 24; break;
		case 'Z': retval = 25; break;
		case '!': retval = 26; break;
		case '?': retval = 27; break;
		case '-': retval = 28; break;
		case '.': retval = 29; break;
		case ',': retval = 30; break;
		case ':': retval = 31; break;
		case ';': retval = 32; break;
		case '\'': retval = 33; break;
		case ' ': retval = 34; break;
		default: retval = -1; break;
	}
	return retval;
}

/* -- GPIO helper function for initialization */
static int init_gpio(
					int gpio, 
					char *label, 
					int flags)
{
	int ret = 0;
	/* -- GPIO request */
	if (gpio_request(gpio, label) != 0) {
		rtdm_printk(KERN_WARNING "init_gpio: Error requesting gpio %d!\n", gpio);
		ret = -1;
	}
	/* -- GPIO mode */
	if (pxa_gpio_mode(gpio | flags) != 0) {
		rtdm_printk(KERN_WARNING "init_gpio: Error setting mode for gpio %d!\n", gpio);
		ret = -1;
	}
	return ret;
}

/* -- RTDM init function */
int __init k2rtdev_init(void)
{
	rtdm_lock_t lock;
	rtdm_lockctx_t lock_ctx;
	
	/* -- reset rotary counter */
	atomic_set(&this.rotary_val, -1);
	
	/* -- reset character value */
	atomic_set(&this.character_val, -1);
	
	/* -- reset show count value */
	atomic_set(&this.showcount_val, 0);
	
	/* -- reset wheelspeed shadow register */
	atomic_set(&this.wheelspeed_val, 0);
	
	/* -- initialize "character written" event  */
	rtdm_event_init(&this.character_written, 0);
	
	/* -- initialize mutex for write operartion */
	rtdm_mutex_init(&this.write_mutex);
	
	/* -- initialize mutex for speed register (PWMDCR3) */
	rtdm_mutex_init(&this.wheelspeed_mutex);
	
	/* -- setup memory mapping for LEDs and potentially other resources */
	this.mem_res = request_mem_region(IO_BASE, MAP_SIZE, "coffee2dev_carme");
	if (this.mem_res == NULL)	{
		printk(KERN_WARNING "k2rtdev_init: request_mem_region failed\n");
		goto fail;
	}
	this.io_base = ioremap_nocache(IO_BASE, MAP_SIZE);
	if (this.io_base == NULL) {
		printk(KERN_WARNING "k2rtdev_init: ioremap_nocache failed\n");
		goto fail;
	}

	/* -- GPIO init for buttons */
	if (init_gpio(GPIO_T0, LABL_T0, GPIO_IN) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_T0, LABL_T0);
		goto fail;
	}
	if (init_gpio(GPIO_T1, LABL_T1, GPIO_IN) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_T1, LABL_T1);
		goto fail;
	}
	if (init_gpio(GPIO_T2, LABL_T2, GPIO_IN) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_T2, LABL_T2);
		goto fail;
	}
	if (init_gpio(GPIO_T3, LABL_T3, GPIO_IN) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_T3, LABL_T3);
		goto fail;
	}
	
	/* -- setup interrupt handlers for buttons */
	if (set_irq_type(gpio_to_irq(GPIO_T0), IRQF_TRIGGER_RISING)) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error calling set_irq_type for GPIO %d / %s\n", GPIO_T0, LABL_T0);
		goto fail;
	}
	if (rtdm_irq_request(&this.irq_t0_h, gpio_to_irq(GPIO_T0), button_irq_handler_rt, 0, device.proc_name, NULL) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error installing RT button irq handler for GPIO %d\n", GPIO_T0);
		goto fail;
	}
	if (set_irq_type(gpio_to_irq(GPIO_T1), IRQF_TRIGGER_RISING)) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error calling set_irq_type for GPIO %d / %s\n", GPIO_T1, LABL_T1);
		goto fail;
	}
	if (rtdm_irq_request(&this.irq_t1_h, gpio_to_irq(GPIO_T1), button_irq_handler_rt, 0, device.proc_name, NULL) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error installing RT button irq handler for GPIO %d\n", GPIO_T1);
		goto fail;
	}
	if (set_irq_type(gpio_to_irq(GPIO_T2), IRQF_TRIGGER_RISING)) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error calling set_irq_type for GPIO %d / %s\n", GPIO_T2, LABL_T2);
		goto fail;
	}
	if (rtdm_irq_request(&this.irq_t2_h, gpio_to_irq(GPIO_T2), button_irq_handler_rt, 0, device.proc_name, NULL) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error installing RT button irq handler for GPIO %d\n", GPIO_T2);
		goto fail;
	}
	if (set_irq_type(gpio_to_irq(GPIO_T3), IRQF_TRIGGER_RISING)) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error calling set_irq_type for GPIO %d / %s\n", GPIO_T3, LABL_T3);
		goto fail;
	}
	if (rtdm_irq_request(&this.irq_t3_h, gpio_to_irq(GPIO_T3), button_irq_handler_rt, 0, device.proc_name, NULL) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error installing RT button irq handler for GPIO %d\n", GPIO_T3);
		goto fail;
	}
	
	/* -- GPIO init for PWM3 speed */
	if (init_gpio(GPIO_PWM3_SPEED, LABL_PWM3_SPEED, GPIO_OUT | GPIO_ALT_FN_2_OUT) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_PWM3_SPEED, LABL_PWM3_SPEED);
	}
	/* -- GPIO init for PWM3 direction */
	if (init_gpio(GPIO_PWM3_DIRECTION, LABL_PWM3_DIRECTION, GPIO_OUT | GPIO_DFLT_HIGH) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_PWM3_DIRECTION, LABL_PWM3_DIRECTION);
	}
	
	/* -- initialize registers for PWM */
	PWMCR3 = 0x14; 	/* divide 13MHz clock by 0x14 */
	PWMDCR3 = 0;   	/* initial speed is zero */
	PWMPCR3 = 100; 	/* periode is 100 time ticks */
	CKEN |= 0x03;	/* enable clock for PWM3 */
	
	/* -- GPIO init for strobo */
	if (init_gpio(GPIO_SSPSCLK, LABL_SSPSCLK, GPIO_OUT | GPIO_ALT_FN_2_OUT) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_SSPSCLK, LABL_SSPSCLK);
		goto fail;
	}
	if (init_gpio(GPIO_SSPSFRM, LABL_SSPSFRM, GPIO_OUT | GPIO_ALT_FN_2_OUT) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_SSPSFRM, LABL_SSPSFRM);
		goto fail;
	}
	if (init_gpio(GPIO_SSPTxD, LABL_SSPTxD, GPIO_OUT | GPIO_ALT_FN_2_OUT) != 0)	{
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_SSPTxD, LABL_SSPTxD);
		goto fail;
	}
	if (init_gpio(GPIO_SSPRxD, LABL_SSPRxD, GPIO_IN | GPIO_ALT_FN_1_IN) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_SSPRxD, LABL_SSPRxD);
		goto fail;
	}
	if (init_gpio(GPIO_SPI_CS, LABL_SPI_CS, GPIO_OUT | GPIO_DFLT_HIGH) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_SPI_CS, LABL_SPI_CS);
		goto fail;
	}
	if (init_gpio(GPIO_INDEX, LABL_INDEX, GPIO_IN) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_INDEX, LABL_INDEX);
		goto fail;
	}
	if (init_gpio(GPIO_ROTARY, LABL_ROTARY, GPIO_IN) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_ROTARY, LABL_ROTARY);
		goto fail;
	}
	if (init_gpio(GPIO_TRIGGER, LABL_TRIGGER, GPIO_OUT | GPIO_DFLT_LOW) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: init_gpio failed for GPIO %d / %s\n", GPIO_TRIGGER, LABL_TRIGGER);
		goto fail;
	}
	
	/* -- setup irq handlers for index */
	if (set_irq_type(gpio_to_irq(GPIO_INDEX), IRQF_TRIGGER_RISING)) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error calling set_irq_type for GPIO %d / %s\n", GPIO_INDEX, LABL_INDEX);
		goto fail;
	}
	if (rtdm_irq_request(&this.irq_index_h, gpio_to_irq(GPIO_INDEX), index_irq_handler_rt, 0, device.proc_name, NULL) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error installing index irq handler %d\n", GPIO_INDEX);
		goto fail;
	}
	
	/* -- setup irq handlers for rotary */
	if (set_irq_type(gpio_to_irq(GPIO_ROTARY), IRQF_TRIGGER_RISING)) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error calling set_irq_type for GPIO %d / %s\n", GPIO_ROTARY, LABL_ROTARY);
		goto fail;
	}
	if (rtdm_irq_request(&this.irq_rotary_h, gpio_to_irq(GPIO_ROTARY), rotary_irq_handler_rt, 0, device.proc_name, NULL) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: Error installing index irq handler %d\n", GPIO_ROTARY);
		goto fail;
	}
	
	/* -- setup SPI for strobo */
	//local_irq_save(flags);
	rtdm_lock_get_irqsave(&lock, lock_ctx);
	
	SSCR0_P1 &= ~SSCR0_SSE;           /* -- disable synchronous serial */
	SSCR0_P1 = SSCR0_DataSize(8);     /* -- data size is 8 bit */
	SSCR1_P1 = SSCR1_SPH | SSCR1_SPO; /* -- Motorola SPI */
	CKEN |= (1 << CKEN_SSP1);         /* -- enable SSP1 Unit Clock */
	SSCR0_P1 |= SSCR0_SSE;            /* -- enable synchronous serial */
	
	SSDR_P1 = 255;					  /* -- max flash duration */
	
	//local_irq_restore(flags);
	rtdm_lock_put_irqrestore(&lock, lock_ctx);
	
	/* -- register device */
	if (rtdm_dev_register(&device) != 0) {
		rtdm_printk(KERN_WARNING "k2rtdev_init: rtdm_dev_register failed %d\n", GPIO_INDEX);
		goto fail;
	}
	
	/* -- log successful initialization */
	rtdm_printk(KERN_INFO "k2rtdev loaded\n");

	return 0;
	
fail:
	/* -- log failure during initialization */
	rtdm_printk(KERN_ERR "k2rtdev failed to load\n");
	k2rtdev_exit();
	return -1;
}

/* -- RTDM exit function */
void __exit k2rtdev_exit(void)
{
	/* -- unregister device, polling at 1000 ms for pending users */
	rtdm_dev_unregister(&device, 1000);
	
	/* -- switch off motor */
	PWMDCR3 = 0;
	
	/* -- destroy "character written" event */
	rtdm_event_destroy(&this.character_written);
	
	/* -- destroy mutex for write operartion */
	rtdm_mutex_destroy(&this.write_mutex);
	
	/* -- destroy mutex for speed register (PWMDCR3) */
	rtdm_mutex_destroy(&this.wheelspeed_mutex);
	
	/* -- free irqs */
	rtdm_irq_disable(&this.irq_t0_h);
	rtdm_irq_free(&this.irq_t0_h);
	rtdm_irq_disable(&this.irq_t1_h);
	rtdm_irq_free(&this.irq_t1_h);
	rtdm_irq_disable(&this.irq_t2_h);
	rtdm_irq_free(&this.irq_t2_h);
	rtdm_irq_disable(&this.irq_t3_h);
	rtdm_irq_free(&this.irq_t3_h);
	rtdm_irq_disable(&this.irq_index_h);
	rtdm_irq_free(&this.irq_index_h);
	rtdm_irq_disable(&this.irq_rotary_h);
	rtdm_irq_free(&this.irq_rotary_h);
	
	/* free gpios */
	gpio_free(GPIO_T0);
	gpio_free(GPIO_T1);
	gpio_free(GPIO_T2);
	gpio_free(GPIO_T3);
	gpio_free(GPIO_PWM3_SPEED);
	gpio_free(GPIO_PWM3_DIRECTION);
	gpio_free(GPIO_SSPSCLK);
	gpio_free(GPIO_SSPSFRM);
	gpio_free(GPIO_SSPTxD);
	gpio_free(GPIO_SSPRxD);
	gpio_free(GPIO_SPI_CS);
	gpio_free(GPIO_INDEX);
	gpio_free(GPIO_ROTARY);
	gpio_free(GPIO_TRIGGER);
	
	/* -- unmap ios */
	iounmap(this.io_base);
	this.io_base = NULL;
	release_mem_region(IO_BASE, MAP_SIZE);
	this.mem_res = NULL;
}

module_init(k2rtdev_init);
module_exit(k2rtdev_exit);

